package org.jflame.commons.net;

import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.concurrent.ConcurrentException;
import org.apache.commons.lang3.concurrent.ConcurrentUtils;
import org.apache.commons.lang3.concurrent.LazyInitializer;
import org.jflame.commons.util.StringHelper;
import org.jflame.commons.valid.LazyPattern;

import java.net.InetAddress;
import java.net.NetworkInterface;
import java.net.SocketException;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import java.util.Optional;
import java.util.function.Predicate;

/**
 * ip地址工具类.
 *
 * @author charles.zhang
 */
public abstract class IPAddressHelper {
    /**
     * 网络回环地址
     */
    public static String[] LOOPBACK_IPS = {"127.0.0.1", "0:0:0:0:0:0:0:1", "::1"};
    private static InetAddressInitializer localIPInitializer = new InetAddressInitializer();

    static class InetAddressInitializer extends LazyInitializer<List<InetAddress>> {
        @Override
        protected List<InetAddress> initialize() throws ConcurrentException {
            try {
                return IPAddressHelper.getAllIPAddr();
            } catch (SocketException e) {
                throw new ConcurrentException(e);
            }
        }
    }

    /**
     * 获取本机所有网络接口地址,不包括虚拟网卡和回环地址
     *
     * @return 返回网络接口地址列表, 可能是空列表
     */
    public static List<InetAddress> getAllPhysicalInetAddress() {
        return ConcurrentUtils.initializeUnchecked(localIPInitializer);
    }

    /**
     * 返回本机所有ip字符串,不包括虚拟网卡和回环地址
     *
     * @return 本机所有ip
     */
    public static String[] getAllPhysicalIP() {
        String[] ipStrArray = null;
        List<InetAddress> localIPAddresses = getAllPhysicalInetAddress();
        ipStrArray = new String[localIPAddresses.size()];
        for (int i = 0; i < ipStrArray.length; i++) {
            ipStrArray[i] = localIPAddresses.get(i)
                    .getHostAddress();
        }

        return ipStrArray;
    }

    /**
     * 获取本机所有网络接口地址
     *
     * @return 返回网络接口地址列表
     * @throws SocketException 读取硬件信息时出现的异常
     */
    public static List<InetAddress> getAllInetAddress() throws SocketException {
        List<InetAddress> ips = new ArrayList<>();
        Enumeration<NetworkInterface> allNetInterfaces = NetworkInterface.getNetworkInterfaces();
        NetworkInterface current = null;
        while (allNetInterfaces.hasMoreElements()) {
            current = allNetInterfaces.nextElement();
            Enumeration<InetAddress> addresses = current.getInetAddresses();
            while (addresses.hasMoreElements()) {
                ips.add(addresses.nextElement());
            }
        }
        return ips;
    }

    private static List<InetAddress> getAllIPAddr() throws SocketException {
        List<InetAddress> ips = new ArrayList<>();
        Enumeration<NetworkInterface> allNetInterfaces = NetworkInterface.getNetworkInterfaces();
        NetworkInterface current = null;
        String[] vmFlags = {"vmware", "virtualbox", "sangfor"};//虚拟网卡标识
        while (allNetInterfaces.hasMoreElements()) {
            current = allNetInterfaces.nextElement();
            if (!current.isUp() || current.isLoopback() || current.isVirtual()
                    || StringUtils.containsAny(current.getDisplayName()
                    .toLowerCase(), vmFlags)) {
                continue;
            }
            Enumeration<InetAddress> addresses = current.getInetAddresses();
            while (addresses.hasMoreElements()) {
                InetAddress addr = addresses.nextElement();
                if (addr.isLoopbackAddress() || addr.isLinkLocalAddress() || addr.isAnyLocalAddress()) {
                    continue;
                }

                ips.add(addr);
            }
        }
        return ips;
    }

    private static Optional<InetAddress> filter(Predicate<InetAddress> predicate) {
        return getAllPhysicalInetAddress().stream()
                .filter(predicate)
                .findFirst();
    }

    /**
     * 返回本机ip地址，优先取外网地址，无外网地址返回局域网地址
     *
     * @return
     */
    public static String getHostIP() {
        Optional<InetAddress> opl = filter(p -> !p.isSiteLocalAddress());
        if (opl.isPresent()) {
            return opl.get()
                    .getHostAddress();
        }
        return getAllPhysicalInetAddress().get(0)
                .getHostAddress();
    }

    /**
     * 获取本机局域网ip地址
     *
     * @return InetAddress
     */
    public static InetAddress getLANNetAddress() {
        Optional<InetAddress> opl = filter(p -> p.isSiteLocalAddress() && !p.isLoopbackAddress());
        if (opl.isPresent()) {
            return opl.get();
        }
        throw new RuntimeException("无法获取任何主机局域网ip");
    }

    /**
     * 获取本机局域网ip字符串
     *
     * @return
     */
    public static String getLANIPString() {
        InetAddress addr = getLANNetAddress();
        return addr.getHostAddress();
    }


    /**
     * 返回本机主机名
     *
     * @return 主机名
     */
    public static String getHostname() {
        return getAllPhysicalInetAddress().get(0)
                .getHostName();
    }

    /**
     * ip bytes 转换为 ip字符串
     *
     * @param ipBytes
     * @return
     * @throws UnknownHostException
     */
    public static String fromBytes(byte[] ipBytes) throws UnknownHostException {
        InetAddress ip = InetAddress.getByAddress(ipBytes);
        return ip.getHostAddress();
    }

    /**
     * 从ip的字符串形式得到字节数组形式.
     *
     * @param ipStr 字符串形式的ip
     * @return 字节数组形式的ip
     * @throws UnknownHostException
     */
    public static byte[] toBytes(String ipStr) throws UnknownHostException {
        if (isIP(ipStr)) {
            InetAddress ip = InetAddress.getByName(ipStr);
            return ip.getAddress();
        } else {
            throw new IllegalArgumentException("不正确的ip:" + ipStr);
        }
    }

    /**
     * ipv4正则
     */
    public final static LazyPattern REGEX_IPV4 = new LazyPattern(
            "([1-9]|[1-9]\\d|1\\d{2}|2[0-4]\\d|25[0-5])(\\.(\\d|[1-9]\\d" + "|1\\d{2}|2[0-4]\\d|25[0-5])){3}");
    /**
     * ipv6 普通格式正则
     */
    public static final LazyPattern REGEX_IPV6_STD = new LazyPattern("^(?:[0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}$");
    /**
     * ipv6压缩格式正则
     */
    public static final LazyPattern REGEX_IPV6_HEX_COMPRESSED = new LazyPattern(
            "^((?:[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4})*)?)" + "::((?:[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4})*)?)$");

    /**
     * 判断是否是ip v4 字符串
     *
     * @param ipv4 ip v4 字符串
     * @return 如果是ipv4返回true
     */
    public static boolean isIPv4(String ipv4) {
        if (StringHelper.isEmpty(ipv4)) {
            return false;
        }
        ipv4 = ipv4.trim();
        return REGEX_IPV4.isMatch(ipv4);
    }

    /**
     * 判断是否是ip v6 字符串
     *
     * @param ipv6 ipv6地址字符
     * @return 如果是ipv6返回true
     */
    public static boolean isIPv6(String ipv6) {
        if (StringHelper.isEmpty(ipv6)) {
            return false;
        }
        ipv6 = ipv6.trim();
        return REGEX_IPV6_STD.isMatch(ipv6) || REGEX_IPV6_HEX_COMPRESSED.isMatch(ipv6);
    }

    /**
     * 判断字符串是否是个ip地址，支持ipv6 or ipv4
     *
     * @param ip ip地址字符
     * @return 如果是ip地址返回true
     */
    public static boolean isIP(String ip) {
        return isIPv4(ip) || isIPv6(ip);
    }

    /**
     * 是否是局域网ip地址，不包括回环ip，广播ip
     *
     * @param ip ip地址字符
     * @return 如果是局域网ip返回true
     */
    public static boolean isLanIP(String ip) {
        if (StringHelper.isEmpty(ip)) {
            return false;
        }
        try {
            InetAddress ipAddr = InetAddress.getByName(ip);
            return ipAddr.isSiteLocalAddress() && !ipAddr.isLoopbackAddress();
        } catch (UnknownHostException e) {
            return false;
        }
    }

    /**
     * 判断ip是否在指定网段内
     *
     * @param ip        ip地址
     * @param ipSegment 指定网段,格式:192.168.0.0/32
     */
    public static boolean isInRange(String ip, String ipSegment) {
        String[] ips = ip.split("\\.");
        int ipAddr = (Integer.parseInt(ips[0]) << 24) | (Integer.parseInt(ips[1]) << 16)
                | (Integer.parseInt(ips[2]) << 8) | Integer.parseInt(ips[3]);
        int type = Integer.parseInt(ipSegment.replaceAll(".*/", ""));
        int mask = 0xFFFFFFFF << (32 - type);
        String cidrIp = ipSegment.replaceAll("/.*", "");
        String[] cidrIps = cidrIp.split("\\.");
        int cidrIpAddr = (Integer.parseInt(cidrIps[0]) << 24) | (Integer.parseInt(cidrIps[1]) << 16)
                | (Integer.parseInt(cidrIps[2]) << 8) | Integer.parseInt(cidrIps[3]);

        return (ipAddr & mask) == (cidrIpAddr & mask);
    }

    /*public static void main(String[] args) {
        System.out.println(getLocalIP());
    }*/
}
